package exceptionlog;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.junit.Test;

/**
 * @author GY
 * 2017年9月25日
 * 牛比吹起来...
 */
/**
 * @author GY
 * 2017年9月25日
 */
public class Exceptionhandle {

    /**
     * GY
     * 2017年9月22日
     *  RuntimeException 可以通过预先检查进行规避
     *  不用try-catch
     */
    @Test
    public void runtimeExceptionNotUseTryCatch(){
        String str = null;
        m1(str);//空指针了
        System.out.println("over");
    }
    @Test
    public void runtimeExceptionNotUseTryCatch2(){
        String str = null;
        m2(str);
        System.out.println("over");
    }
    //异常
    public void m1(String str){
    	int i = str.indexOf("111");
    }
    //建议
    //两个原因:1、性能及开销;2、这类异常本就不应该在运行中出现(编程中就能规避)，而不是等出现异常后再处理异常。
    public void m2(String str){
    	if(str == null){
    		return;//不太业务场景不同处理方式
    	}
    	int i = str.indexOf("111");
    	//略
    }
    
    
    /**
     * 追求卓越成功自然相随
     * 2017年9月24日
     * 功能:不要用是否异常做条件控制
     */
    //错误使用
    @Test
    public void dontUseExceptionControlProcedure(){
    	SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
    	String strDate = "2017|09|16";
    	Date date = null;
    	try {
    	    date = sdf.parse(strDate);
    	    System.out.println("sdf类型");
        } catch (Exception e) {
            e.printStackTrace();
            SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd");
            try {
                date = sdf2.parse(strDate);
                System.out.println("sdf2类型");
            } catch (Exception e2) {
                e2.printStackTrace();
                SimpleDateFormat sdf3 = new SimpleDateFormat("yyyy.MM.dd");
                try {
                    date = sdf3.parse(strDate);
                    System.out.println("sdf3类型");
                } catch (Exception e3) {
                    e3.printStackTrace();
                    System.out.println("无法转化");
                    return;
                }
            }
        }
    	//......后续使用date对象
    	System.out.println(date.getTime());
    }
    
    /**
     * 使用代码条件控制流程
     * 不用异常控制流程
     * GY
     * 2017年9月25日
     */
    //推荐使用  定义正则表达式判断     因为异常的处理效率比条件分支低
    //出现异常后再执行某段代码  跟  提前 判断进入某段代码的 区别。
    @Test
    public void dontUseExceptionControlProcedure2(){
        //建议将此段正则校验 封装 因为有可能其他业务场景也需要
        String strDate = "2017-02-18";
        String regDate = "[0-9]{4}[\\-\\/\\s\\年\\.]?((0[1-9])|(1[0-2]))[\\-\\/\\s\\月\\.]?(([0-2][1-9])|(3[0|1]))[日\\s]?";
        Pattern pattern = Pattern.compile(regDate);
        Matcher m = pattern.matcher(strDate);
        
        if(!m.matches()){
            System.out.println("字符串无法转化日期");//抛出自定义异常
            return;
        }
        SimpleDateFormat sdf = null;
        if((strDate.indexOf("年")) != -1){
            sdf = new SimpleDateFormat("yyyy年MM月dd日");
            System.out.println("年月日");
        }
        if((strDate.indexOf("-")) != -1 && sdf == null){
            sdf = new SimpleDateFormat("yyyy-MM-dd");
            System.out.println("yyyy-MM-dd");
        }
        if((strDate.indexOf(".")) != -1  && sdf == null){
            sdf = new SimpleDateFormat("yyyy.MM.dd");
            System.out.println("yyyy.MM.dd");
        }
        if((strDate.indexOf("/")) != -1  && sdf == null){
            sdf = new SimpleDateFormat("yyyy/MM/dd");
            System.out.println("yyyy/MM/dd");
        }
        if(sdf == null){
            sdf = new SimpleDateFormat("yyyyMMdd");
            System.out.println("yyyyMMdd");
        }
        Date date = null;
        try {
            date = sdf.parse(strDate);
        } catch (ParseException e) {
            e.printStackTrace();
            System.out.println("太奇葩！");
            return;
        }
        //......后续使用date对象
        System.out.println(date.getTime());
    }
    
    /**
     * try代码块能小则小
     * 稳定代码不应包含在try中
     * 不稳定的代码catch区分异常种类
     * 因为不同的异常类型不同的处理方式
     * GY
     * 2017年9月25日
     */
    //(...)代表瞎bb
    //原因: try执行代码的效率会有所下降,try得越多另外开辟的空间耗损越多(...)  见上例
    //     不同的异常类型不同的处理方式,方便分析异常日志,针对不同的业务异常,解决方案更加具体(...)
    @Test
    public void minTryCatchBlock(){
        //这里演示不同异常不同解决方案 
        //参数异常不计录,业务异常进行记录
        try {
            handleService1("GY","1",new BigDecimal(500),123L);
        } catch (MyParameterException e) {
            e.printStackTrace();
            //封装异常信息返回给用户
            return;
        } catch (MyBusinessException e) {
            e.printStackTrace();
            //记录日志.........业务异常记录日志(用户操作异常表新增)
            Long userId = (Long)(e.getData());
            System.out.println("异常用户id:"+userId);
            //封装异常信息返回给用户
            return;
        }
        
    }
    /**
     * @throws MyParameterException
     * @throws MyBusinessException
     * GY
     * 2017年9月25日
     * 针对某个业务的处理类
     */
    public void handleService1(String name, String loanState, BigDecimal money, Long userId) 
            throws MyParameterException, MyBusinessException{
        met1(name);
        met2(loanState, money,userId);
    }
    
    /**
     * @param s
     * @throws MyParameterException
     * GY
     * 2017年9月25日
     * 操作1
     */
    public void met1(String name) throws MyParameterException{
        if(StringUtils.isBlank(name)){
            throw new MyParameterException("0001");
        }
        //.....
    }
    /**
     * @param loanState
     * @param money
     * @throws MyBusinessException
     * GY
     * 2017年9月25日
     * 操作2
     */
    public void met2(String loanState, BigDecimal money, Long userId) throws MyBusinessException{
        if("1".equals(loanState)){
            MyBusinessException e = new MyBusinessException("9002");
            e.setData(userId);
            throw e;
        }
        //.....
    }
    
    /*
     * 捕获意味着要处理,不想处理请不要捕获,抛出给外面的有处理能力的处理,最外层必须捕获,将异常转化为用户能理解的信息
     * 捕获了异常就得处理异常,处理方式根据业务需求而定。
     * 如果本方法中出现异常,而又想继续执行catch异常后的代码,
     * 则必须在本方法处理异常,处理后继续执行catch(){}后代码
     */
    
    /**
     * 事务绑定的代码块如果异常,需要catch中手动回滚rollback()
     * GY
     * 2017年9月25日
     * spring @Transaction注解方法体中catch到异常,是否需要手动回滚待讨论(如何回滚？)
     */
    public void whenCatchExceptionNeedRollBackTransaction(){
        Connection conn = null;
        Statement stmt = null;
        try {
            // 动态导入数据库的驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 获取数据库链接
            conn = DriverManager.getConnection(
                   "jdbc:mysql://192.168.1.108:3306/data_warehouse",
                   "root", 
                   "Mimidai123");
            // 开启事务
            //不把其设置为true之前都是一个当作一个事务来处理
            conn.setAutoCommit( false );
            // 创造SQL语句
            String sql = "INSERT INTO myrollback ( name,age,address,school ) VALUES ( 'test', 22,'大立即','hdu' )";
            String sql2 = "INSERT INTO myrollback ( name,age,address,school ) VALUES ( 'test_Name', '33','大立即','hdu' ,'test')";
            // 执行SQL语句
            stmt = conn.createStatement();
            stmt.executeUpdate(sql);
            stmt.executeUpdate(sql2);
            // 提交事务
            conn.commit();
            System.out.println( "OK!" );
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("有错误！");
            try {
           // 回滚事务
           //撤销上面对事务的所有操作哈！
               conn.rollback();
            } catch ( Exception e2 ) {e2.printStackTrace();}
        } finally {
            // 关闭Statement
            if(stmt!=null){
                try {
                    stmt.close();
                } catch (Exception e) {e.printStackTrace();}
            }
            // 关闭Connection
            if(conn!=null){
                try {
                    conn.close();
                } catch (Exception e) {e.printStackTrace();}
            }
        }
    }
    
    /**
     * finally中需要释放打开的资源
     */
    @Test
    public void finallyNeedCloseResource(){
    	File file = new File("F:\\javaeeWorkspace\\exceptionLog\\test.txt");
    	if(file.exists()){
    		file.delete();
    	}
    	FileOutputStream fos = null;
    	PrintStream  isr = null;
    	PrintWriter pr = null;
    	try {
			fos = new FileOutputStream(file);
			isr = new PrintStream (fos);
			pr = new PrintWriter(isr);
			pr.print("7777777777");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} finally {
			if(pr != null){
				pr.close();
			}
			if(isr != null){
				isr.close();
			}
			if(fos != null){
				try {
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
    }
    /**
     * jdk1.7及以上可以用try-with-resources
     * 出try体资源即被关闭
     */
    @Test
    public void finallyNeedCloseResource2(){
    	File file = new File("F:\\javaeeWorkspace\\exceptionLog\\test.txt");
    	if(file.exists()){
    		file.delete();
    	}
    	try(FileOutputStream fos = new FileOutputStream(file);
    			PrintWriter  pr = new PrintWriter(fos)){
    		pr.print("7777777777");
    		System.out.println("000");
    		return;
    	} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			//return;
			System.out.println("try-catch块无论异常与否的终点");
		}
    	System.out.println("出现异常,异常被处理后才会执行的代码");
    }
    
    /*
     * 8、
     * 对于异常处理机制一样的不通的异常可以catch这两种异常的父类异常
     * 最好抛什么异常捕获(catch)什么异常,统一用Exception不方便区分,不便于区分处理方式
     */
    
    /**
     * 9、
     * 可以为null
     * 必需说明白，方法什么情况会返回null
     */
    @Test
    public void returnNull(){
    	//比如数据库删除一条记录,在进行删除完对象对应的数据库数据操作后,可以将对象置为null
    	//return 这个引用(null)
    }
    
    /**
     * 10、
     * 防止出现NPE
     */
    @Test
    public void avoidNPE(){
    	method1(999L);
    }
    //反例 错误                      解决:返回值类型跟方法返回类型保持一致
    public long method1(Long taskId){
    	//Long userId = repayDao.findUserIdByTaskId(taskId);
    	//如果没有这个字段或没有这条数据    数据库查询结果为null
    	Long userId = null;
    	return userId;//会有警告提示
    }
    //JDK8 Optional  obj.isPresent() 与 obj != null 无任何分别
    
    /**
     * 10、
     * 集合里的元素isNotEmpty 取出的这个元素仍然有可能为null
     * GY
     * 2017年9月26日
     */
    @Test
    public void avoidNPE2(){
        List<String> list = new ArrayList<String>();
        list.add(null);
        boolean isEmpty = (list.isEmpty());
        System.out.println(isEmpty);        //false 非空
        System.out.println(list.get(0));    //null  集合中含有一个null元素,集合非空
    }
    
    /*
     * 11、
     * 避免直接抛出 new RuntimeException()
     * unchecked异常 运行时异常 控制组 数组越界  (....)  代码规避,不让出现
     * checked 异常    Java的设计哲学：没有完善错误处理的代码根本没有机会被执行。  需要具备处理这种异常的代码
     * 更不允许抛出 Exception 或者 Throwable
     */
    
    /*
     * 12、
     * RPC调用:远程过程调用协议
     *        一种通过网络从远程计算机程序上请求服务，而不需要了解底层网络技术的协议。
     * 
     */
    
    /*
     * 13、
     * DRY 原则
     * 不要重复自己"。
     * 强调的意思就是在进行编程时相同的代码不要重复写，最好只写一次，然后可以在其他地方直接引用。
     * 一个类中有多个 public 方法，都需要进行数行相同的参数校验操作，这个时候可以抽取成一个公共校验方法
     * 必须在某项操作前校验(执行某段代码(即使代码为空)),也有必要加上。面向切面编程。(....)  资金类交易
     */
    
}

/**
 * @author GY
 * 2017年9月25日
 * 入参校验异常类
 */
@SuppressWarnings("serial")
class MyParameterException extends Exception{
    
    /**
     * 异常码
     */
    private String code;
    /**
     * 异常错误提示
     */
    private String msg;
    /**
     * 异常对象
     */
    private Object data;//比如异常json对象  后期捕获异常后处理的数据依据
    
    /**
     * 可维护异常信息map
     */
    private static final HashMap<String, String> MY_PARAMETER_EXCEPTION_CODE_MSG_MAP = new HashMap<String, String>();
    static{
        MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.put("0001", "参数不能为空");//必要字段传空
        MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.put("0002", "参数不符合业务逻辑");//比如不可能为负数的字段为负数
        MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.put("0003", "参数内容不合法");//比如日期格式不合规
        MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.put("0004", "不符合接口参数结构");//比如接口json报文解析失败
    }
    
    /**
     * @param code异常码
     */
    public MyParameterException(String code){
        this.code = code;
        this.msg = MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.get(this.code);
    }
    
    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }

    @Override
    public void printStackTrace() {
        System.out.println(this.code+":"+this.msg);
    }
    
    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return (this.code+":"+this.msg);
    }
    
}

/**
 * @author GY
 * 2017年9月25日
 * 业务处理异常类
 */
@SuppressWarnings("serial")
class MyBusinessException extends Exception{
    
    /**
     * 异常码
     */
    private String code;
    /**
     * 异常错误提示
     */
    private String msg;
    /**
     * 异常对象
     */
    private Object data;//比如异常json对象
    
    /**
     * 可维护异常信息map
     */
    private static final HashMap<String, String> MY_PARAMETER_EXCEPTION_CODE_MSG_MAP = new HashMap<String, String>();
    static{
        MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.put("9001", "借款信息不存在");//不存在该用户的欠款信息
        MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.put("9002", "用户已经还款");//数据库已经还了又调了还款接口还钱
    }
    /**
     * @param code异常码
     */
    public MyBusinessException(String code){
        this.code = code;
        this.msg = MY_PARAMETER_EXCEPTION_CODE_MSG_MAP.get(this.code);
    }
    
    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
    public static HashMap<String, String> getMyParameterExceptionCodeMsgMap() {
        return MY_PARAMETER_EXCEPTION_CODE_MSG_MAP;
    }
    
    @Override
    public void printStackTrace() {
        System.out.println(this.code+":"+this.msg);
    }

    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return (this.code+":"+this.msg);
    }
    
    
    
}

